#include "Core.h"

Core* game = 0; // zero game pointer

Core::Core(HINSTANCE hInstance, int nShowCmd) // constructor
{
	this->hInstance = hInstance;						// set value to application instance handle
	createWindow(nShowCmd);								// calls create window method
	initDirectX();										// calls initalize directx method
	inputHandler = new InputHandler(hInstance, hWnd);	// create inputHandler object
	PrepareXACT(); // Initialise XACT Audio Engine
}

Core::~Core() // destructor
{
	delete inputHandler;	// remove objects
	inputHandler = 0;		// zero memory location
	CleanupXACT(); // Unload XACT Audio Engine from memory.
	sprite->Release();		// clear used resource
	sprite = 0;
	device->Release();
	device = 0;
	d3d->Release();
	d3d = 0;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) // window event handler
{
	if (game != 0)											// check value is not zero
		return game->msgProc(hWnd, msg, wParam, lParam);	// call event handler
	else
		return DefWindowProc(hWnd, msg, wParam, lParam);	// default
}

void Core::createWindow(int nShowCmd) // create window
{
	WNDCLASSEX wc;							// declare window class object wc
	ZeroMemory(&wc, sizeof(WNDCLASSEX));		// zero memory location of window object

	wc.cbSize = sizeof(WNDCLASSEX);				// size in bytes of structure
	wc.lpfnWndProc = WndProc;					// pointer to window procedure
	wc.hInstance = hInstance;					// handle to instance containing window procedure
	wc.hIcon = LoadIcon(0, IDI_APPLICATION);	// handle to icon resource (default if NULL)
	wc.hCursor = LoadCursor(0, IDC_ARROW);		// handle to cursor resource
	wc.hbrBackground = (HBRUSH)COLOR_WINDOW;	// handle to background brush or colour value
	wc.lpszClassName = L"AppJam";		// pointer to string specifying resource name of class menu

	RegisterClassEx(&wc);	// register window object with values from wc

	hWnd = CreateWindowEx(NULL,					// extended window style (ie drag & drop)
						  L"AppJam",	// window class name
						  L"AppJam",	// window name
						  WS_OVERLAPPEDWINDOW,	// window sytle being created
						  CW_USEDEFAULT,		// initial x co-ord of window
						  CW_USEDEFAULT,		// initial y co-ord of window
						  1296,					// window width, add 16 to account for window frame.
						  758,					// window height, add 38 to account for window frame.
						  NULL,					// handle to parent window
						  NULL,					// handle to menu or child window identifier (style dependant)
						  hInstance,			// handle to application instance
						  NULL);				// handle to initialisation procedure

	ShowWindow(hWnd, nShowCmd);		// set window show state
	UpdateWindow(hWnd);				// call update function 
}

// EXPAND THE EVENT MESSAGE HANDLER TO DEAL WITH WINDOW FOCUS
LRESULT Core::msgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) // event message handler
{
	switch (msg)		// Does not handle window focus i.e. clicking off window will stop inputHandler
	{
	case WM_CLOSE:				// if close message recieved
		DestroyWindow(hWnd);	// destroying the window creates destroy message
		break;
	case WM_DESTROY:			// if destroy message recieved
		PostQuitMessage(0);		// quit message closes application
		break;
	default:
		return DefWindowProc(hWnd, msg, wParam, lParam); // default
	}
	return 0;
}

void Core::initDirectX()	// initialise directx
{
	d3d = Direct3DCreate9(D3D_SDK_VERSION);		// declare directx device

	D3DPRESENT_PARAMETERS d3dpp;				// set directx device parameters
	ZeroMemory(&d3dpp, sizeof(d3dpp));			// zero memory location
	d3dpp.Windowed = true;						// in window
	d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;	// 
	d3dpp.hDeviceWindow = hWnd;					// specify directx window

	d3d->CreateDevice(D3DADAPTER_DEFAULT,		// create device object on primary display adapter
					  D3DDEVTYPE_HAL,			// device type hardware rasterization
					  hWnd,						// focus window alerts directx when in foreground/background mode
					  D3DCREATE_SOFTWARE_VERTEXPROCESSING,		// software vertex processing ?? why not hardware
					  &d3dpp,					// pointer to device parameters
					  &device);					// pointer to device

	D3DXCreateSprite(device, &sprite);
}

//-----------------------------------------------------------------------------------------
// This function does the following:
//
//      1. Initialize XACT by calling pEngine->Initialize 
//      2. Create the XACT wave bank(s) you want to use
//      3. Create the XACT sound bank(s) you want to use
//      4. Store indices to the XACT cue(s) your game uses
//-----------------------------------------------------------------------------------------
HRESULT Core::PrepareXACT()
{
    HRESULT hr;
    WCHAR str[MAX_PATH];
    HANDLE hFile;
    DWORD dwFileSize;
    DWORD dwBytesRead;
    HANDLE hMapFile;

    // Clear struct
    ZeroMemory( &audioState, sizeof( AudioState ) );

    hr = CoInitializeEx( NULL, COINIT_MULTITHREADED );  // COINIT_APARTMENTTHREADED will work too
    if( SUCCEEDED( hr ) )
    {
        // Switch to auditioning mode based on command line.  Change if desired
        bool bAuditionMode = DoesCommandLineContainAuditionSwitch();
        bool bDebugMode = false;

        DWORD dwCreationFlags = 0;
        if( bAuditionMode ) dwCreationFlags |= XACT_FLAG_API_AUDITION_MODE;
        if( bDebugMode ) dwCreationFlags |= XACT_FLAG_API_DEBUG_MODE;

        hr = XACT3CreateEngine( dwCreationFlags, &audioState.pEngine );
    }
    if( FAILED( hr ) || audioState.pEngine == NULL )
        return E_FAIL;

    // Initialize & create the XACT runtime 
    XACT_RUNTIME_PARAMETERS xrParams = {0};
    xrParams.lookAheadTime = XACT_ENGINE_LOOKAHEAD_DEFAULT;
    hr = audioState.pEngine->Initialize( &xrParams );
    if( FAILED( hr ) )
        return hr;

    if( FAILED( hr = FindMediaFileCch( str, MAX_PATH, L"sounds.xwb" ) ) )
        return hr;

    // Create an "in memory" XACT wave bank file using memory mapped file IO
    // Memory mapped files tend to be the fastest for most situations assuming you 
    // have enough virtual address space for a full map of the file
    hr = E_FAIL; // assume failure
    hFile = CreateFile( str, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL );
    if( hFile != INVALID_HANDLE_VALUE )
    {
        dwFileSize = GetFileSize( hFile, NULL );
        if( dwFileSize != -1 )
        {
            hMapFile = CreateFileMapping( hFile, NULL, PAGE_READONLY, 0, dwFileSize, NULL );
            if( hMapFile )
            {
                audioState.pbWaveBank = MapViewOfFile( hMapFile, FILE_MAP_READ, 0, 0, 0 );
                if( audioState.pbWaveBank )
                {
                    hr = audioState.pEngine->CreateInMemoryWaveBank( audioState.pbWaveBank, dwFileSize, 0,
                                                                       0, &audioState.pWaveBank );
                }
                CloseHandle( hMapFile ); // pbWaveBank maintains a handle on the file so close this unneeded handle
            }
        }
        CloseHandle( hFile ); // pbWaveBank maintains a handle on the file so close this unneeded handle
    }
    if( FAILED( hr ) )
        return E_FAIL; // CleanupXACT() will cleanup state before exiting

    // Read and register the sound bank file with XACT.  Do not use memory mapped file IO because the 
    // memory needs to be read/write and the working set of sound banks are small.
    if( FAILED( hr = FindMediaFileCch( str, MAX_PATH, L"sounds.xsb" ) ) )
        return hr;
    hr = E_FAIL; // assume failure
    hFile = CreateFile( str, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL );
    if( hFile != INVALID_HANDLE_VALUE )
    {
        dwFileSize = GetFileSize( hFile, NULL );
        if( dwFileSize != -1 )
        {
            // Allocate the data here and free the data when recieving the sound bank destroyed notification
            audioState.pbSoundBank = new BYTE[dwFileSize];
            if( audioState.pbSoundBank )
            {
                if( 0 != ReadFile( hFile, audioState.pbSoundBank, dwFileSize, &dwBytesRead, NULL ) )
                {
                    hr = audioState.pEngine->CreateSoundBank( audioState.pbSoundBank, dwFileSize, 0,
                                                                0, &audioState.pSoundBank );
                }
            }
        }
        CloseHandle( hFile );
    }
    if( FAILED( hr ) )
        return E_FAIL; // CleanupXACT() will cleanup state before exiting

    // Get the sound cue index from the sound bank
    //
    // Note that if the cue does not exist in the sound bank, the index will be XACTINDEX_INVALID
    // however this is ok especially during development.  The Play or Prepare call will just fail.
    audioState.iZap = audioState.pSoundBank->GetCueIndex( "zap" );

    return S_OK;
}

//-----------------------------------------------------------------------------
// Releases all previously initialized XACT objects
//-----------------------------------------------------------------------------
void Core::CleanupXACT()
{
    // Shutdown XACT
    //
    // Note that pEngine->ShutDown is synchronous and will take some time to complete 
    // if there are still playing cues.  Also pEngine->ShutDown() is generally only 
    // called when a game exits and is not the preferred method of changing audio 
    // resources. To know when it is safe to free wave/sound bank data without 
    // shutting down the XACT engine, use the XACTNOTIFICATIONTYPE_SOUNDBANKDESTROYED 
    // or XACTNOTIFICATIONTYPE_WAVEBANKDESTROYED notifications 
	if( audioState.pEngine )
    {
        audioState.pEngine->ShutDown();
        audioState.pEngine->Release();
    }

    if( audioState.pbSoundBank )
        delete[] audioState.pbSoundBank;
    audioState.pbSoundBank = NULL;

    // After pEngine->ShutDown() returns it is safe to release memory mapped files
    if( audioState.pbWaveBank )
        UnmapViewOfFile( audioState.pbWaveBank );
    audioState.pbWaveBank = NULL;

    CoUninitialize();
}

//--------------------------------------------------------------------------------------
// Helper function to try to find the location of a media file
//--------------------------------------------------------------------------------------
HRESULT Core::FindMediaFileCch( WCHAR* strDestPath, int cchDest, LPCWSTR strFilename )
{
    bool bFound = false;

    if( NULL == strFilename || strFilename[0] == 0 || NULL == strDestPath || cchDest < 10 )
        return E_INVALIDARG;

    // Get the exe name, and exe path
    WCHAR strExePath[MAX_PATH] = {0};
    WCHAR strExeName[MAX_PATH] = {0};
    WCHAR* strLastSlash = NULL;
    GetModuleFileName( NULL, strExePath, MAX_PATH );
    strExePath[MAX_PATH - 1] = 0;
    strLastSlash = wcsrchr( strExePath, TEXT( '\\' ) );
    if( strLastSlash )
    {
        StringCchCopy( strExeName, MAX_PATH, &strLastSlash[1] );

        // Chop the exe name from the exe path
        *strLastSlash = 0;

        // Chop the .exe from the exe name
        strLastSlash = wcsrchr( strExeName, TEXT( '.' ) );
        if( strLastSlash )
            *strLastSlash = 0;
    }

    StringCchCopy( strDestPath, cchDest, strFilename );
    if( GetFileAttributes( strDestPath ) != 0xFFFFFFFF )
        return S_OK;

    // Search all parent directories starting at .\ and using strFilename as the leaf name
    WCHAR strLeafName[MAX_PATH] = {0};
    StringCchCopy( strLeafName, MAX_PATH, strFilename );

    WCHAR strFullPath[MAX_PATH] = {0};
    WCHAR strFullFileName[MAX_PATH] = {0};
    WCHAR strSearch[MAX_PATH] = {0};
    WCHAR* strFilePart = NULL;

    GetFullPathName( L".", MAX_PATH, strFullPath, &strFilePart );
    if( strFilePart == NULL )
        return E_FAIL;

    while( strFilePart != NULL && *strFilePart != '\0' )
    {
        StringCchPrintf( strFullFileName, MAX_PATH, L"%s\\%s", strFullPath, strLeafName );
        if( GetFileAttributes( strFullFileName ) != 0xFFFFFFFF )
        {
            StringCchCopy( strDestPath, cchDest, strFullFileName );
            bFound = true;
            break;
        }

        StringCchPrintf( strFullFileName, MAX_PATH, L"%s\\Tutorials\\%s\\%s", strFullPath, strExeName, strLeafName );
        if( GetFileAttributes( strFullFileName ) != 0xFFFFFFFF )
        {
            StringCchCopy( strDestPath, cchDest, strFullFileName );
            bFound = true;
            break;
        }

        StringCchPrintf( strSearch, MAX_PATH, L"%s\\..", strFullPath );
        GetFullPathName( strSearch, MAX_PATH, strFullPath, &strFilePart );
    }
    if( bFound )
        return S_OK;

    // On failure, return the file as the path but also return an error code
    StringCchCopy( strDestPath, cchDest, strFilename );

    return HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
}


//--------------------------------------------------------------------------------------
bool Core::DoesCommandLineContainAuditionSwitch()
{
    const WCHAR* strAuditioning = L"-audition"; size_t nArgLen; int nNumArgs;
    LPWSTR* pstrArgList = CommandLineToArgvW( GetCommandLine(), &nNumArgs );
    for( int iArg = 1; iArg < nNumArgs; iArg++ )
    {
        StringCchLength( pstrArgList[iArg], 256, &nArgLen );
        if( _wcsnicmp( pstrArgList[iArg], strAuditioning, nArgLen ) == 0 && nArgLen == 9 )
            return true;
    }
    LocalFree( pstrArgList );
    return false;
}